package demo.java.util.concurrent.locks;

import demo.java.lang.ThreadDemo;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * <h1>读写锁 ReentrantReadWriteLock</h1>
 * 读写锁的理念是，只要没有任何线程写入变量，并发读取可变变量通常是安全的。
 * 所以读锁可以同时被多个线程持有，只要没有线程持有写锁。这样可以提升性能和吞吐量，因为读取比写入更加频繁。
 * <p>
 * 读读共享，写写互斥，读写互斥
 */
public class ReadWriteLockDemo {

    public static final Logger LOGGER = LoggerFactory.getLogger(ReadWriteLockDemo.class);


    @Test
    public void demoReentrantReadWriteLock() {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Map<String, String> map = new HashMap<>();
        ReadWriteLock lock = new ReentrantReadWriteLock();

        executor.submit(() -> {
            lock.writeLock().lock();
            try {
                Thread.sleep(1);
                map.put("foo", "bar");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.writeLock().unlock();
            }
        });

        Runnable readTask = () -> {
            lock.readLock().lock();
            try {
                System.out.println(map.get("foo"));
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
        };

        executor.submit(readTask);
        executor.submit(readTask);

        ThreadDemo.safeSleep(10_000L);
        executor.shutdown();
    }

    static void demo1() {
        final TheData myData = new TheData(); // 这是各线程的共享数据
        for (int i = 0; i < 3; i++) { // 开启3个读线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        myData.get();
                    }
                }
            }).start();
        }
        for (int i = 0; i < 3; i++) { // 开启3个写线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        myData.put(new Random().nextInt(10000));
                    }
                }
            }).start();
        }
    }
}

class TheData {

    private Object data = null;
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void get() {
        readWriteLock.readLock().lock(); // 读锁开启，读线程均可进入
        try { // 用try finally来防止因异常而造成的死锁
            System.out.println(Thread.currentThread().getName() + "is ready to read");
            Thread.sleep(new Random().nextInt(100));
            System.out.println(Thread.currentThread().getName() + "have read date" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock(); // 读锁解锁
        }
    }

    public void put(Object data) {
        readWriteLock.writeLock().lock(); // 写锁开启，这时只有一个写线程进入
        try {
            System.out.println(Thread.currentThread().getName() + "is ready to write");
            Thread.sleep(new Random().nextInt(100));
            this.data = data;
            System.out.println(Thread.currentThread().getName() + "have write date" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock(); // 写锁解锁
        }
    }
}
